package com.symaster.common.config;

import com.alibaba.fastjson.JSONObject;
import com.symaster.common.thread.WatchThread;
import com.symaster.common.util.FileUtil;
import com.symaster.common.util.PropertiesUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.File;
import java.util.*;

/**
 * @author yinmiao
 * @date 2022-02-12 13:51
 */
public class Config {

    private final String fileName;
    private Map<String, String> configMap;
    private Map<String, String> defaultConfig;
    private Integer saveInterval;

    public Config(String fileName) {
        this.fileName = fileName;
    }

    public Config(String fileName, Integer saveInterval) {
        this.fileName = fileName;
        this.saveInterval = saveInterval;
        if (this.saveInterval != null && this.saveInterval > 0) {
            new WatchSave(this, saveInterval).start();
        }
    }

    public Config(String fileName, Map<String, String> defaultConfig) {
        this.fileName = fileName;
        this.defaultConfig = defaultConfig;
    }

    public Config(String fileName, Map<String, String> defaultConfig, Integer saveInterval) {
        this.fileName = fileName;
        this.defaultConfig = defaultConfig;
        this.saveInterval = saveInterval;
        if (this.saveInterval != null && this.saveInterval > 0) {
            new WatchSave(this, saveInterval).start();
        }
    }

    public synchronized String get(String key) {
        if (configMap == null) {
            reload();
        }
        return configMap.get(key);
    }

    public Map<String, String> findByContains(String key) {
        if (configMap == null) {
            reload();
        }
        HashMap<String, String> resultMap = new HashMap<>();
        configMap.forEach((k, v) -> {
            if (k.toLowerCase().contains(key.toLowerCase())) {
                resultMap.put(k, v);
            }
        });
        return resultMap;
    }

    public Map<String, String> findByStartWith(String key) {
        if (configMap == null) {
            reload();
        }
        HashMap<String, String> resultMap = new HashMap<>();
        configMap.forEach((k, v) -> {
            if (k.toLowerCase().startsWith(key.toLowerCase())) {
                resultMap.put(k, v);
            }
        });
        return resultMap;
    }

    public Map<String, String> findByEndWith(String key) {
        if (configMap == null) {
            reload();
        }
        HashMap<String, String> resultMap = new HashMap<>();
        configMap.forEach((k, v) -> {
            if (k.toLowerCase().endsWith(key.toLowerCase())) {
                resultMap.put(k, v);
            }
        });
        return resultMap;
    }

    public List<String> getArray(String key) {
        String s = get(key);
        if (StringUtils.isEmpty(s)) {
            return null;
        }
        return JSONObject.parseArray(s, String.class);
    }

    public <T> List<T> getArray(String key, Class<T> clazz) {
        String s = get(key);
        if (StringUtils.isEmpty(s)) {
            return null;
        }
        return JSONObject.parseArray(s, clazz);
    }

    public synchronized String remove(String key) {
        if (configMap == null) return null;
        return configMap.remove(key);
    }

    public Set<String> keySet() {
        if (configMap == null) {
            return null;
        }
        return configMap.keySet();
    }

    private File getConfigFile() {
        return new File(fileName);
    }

    public synchronized void reload() {
        File file = getConfigFile();
        String tempFilePath = FileUtil.getTempFilePath(file.getPath());
        File tempFile = new File(tempFilePath);
        if (tempFile.isFile()) {
            boolean delete = file.delete();
            boolean b = tempFile.renameTo(file);
        }

        if (!file.isFile()) {
            configMap = defaultConfigIsEmpty() ? new HashMap<>() : defaultConfig;
            PropertiesUtils.saveProps(file.getPath(), configMap);
            return;
        }
        configMap = PropertiesUtils.loadProps(file.getPath());
        if (!defaultConfigIsEmpty()) if (repairConfig()) save();
    }

    public void save() {
        if (saveInterval != null && saveInterval > 0) {
            return;
        }
        _save();
    }

    public synchronized void _save() {
        String path = getConfigFile().getPath();

        boolean rename = false;
        String backFilePath = null;
        try {
            File file = new File(path);
            if (file.isFile()) {
                backFilePath = FileUtil.getTempFilePath(path);
                rename = file.renameTo(new File(backFilePath));
            }
            PropertiesUtils.saveProps(path, configMap);
            if (StringUtils.isNotEmpty(backFilePath)) {
                boolean delete = new File(backFilePath).delete();
            }
        } catch (Exception e) {
            if (rename) {
                File file = new File(path);
                boolean delete = file.delete();
                File backFile = new File(backFilePath);
                boolean b = backFile.renameTo(file);
            }
            throw new RuntimeException(e);
        }
    }

    private String backConfig(String path) {
        String backPath = FileUtil.getTempFilePath(path);
        FileUtil.copyFile(path, backPath);
        return backPath;
    }

    public synchronized void put(String key, String value) {
        if (configMap == null) {
            reload();
        }
        configMap.put(key, value);
    }

    public void putArray(String key, List<String> values) {
        put(key, JSONObject.toJSONString(values));
    }

    public void putArray(String key, String value) {
        List<String> array = getArray(key);
        if (array == null) {
            array = new ArrayList<>();
        }
        array.add(value);
        putArray(key, array);
    }

    private boolean defaultConfigIsEmpty() {
        return defaultConfig == null || defaultConfig.size() <= 0;
    }

    private boolean repairConfig() {
        boolean flag = false;
        for (String defaultKey : defaultConfig.keySet()) {
            if (StringUtils.isEmpty(configMap.get(defaultKey))) {
                configMap.put(defaultKey, defaultConfig.get(defaultKey));
                flag = true;
            }
        }
        return flag;
    }

    static class WatchSave extends WatchThread {
        private final Config config;

        public WatchSave(Config config, int saveInterval) throws IllegalArgumentException {
            super(saveInterval, "ConfigWatchThread_" + UUID.randomUUID());
            this.config = config;
        }

        @Override
        protected void execute() throws Exception {
            config._save();
        }
    }
}
